﻿//#define USE_DUNGEONBUDDY_DLL
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using CommonBehaviors.Actions;
using Styx;
using Styx.Common.Helpers;
using Styx.CommonBot;
using Styx.CommonBot.Frames;
using Styx.CommonBot.Routines;
using Styx.Helpers;
using Styx.Patchables;
using Styx.Pathing;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;
#if USE_DUNGEONBUDDY_DLL
using Bots.DungeonBuddyDll;
using Bots.DungeonBuddyDll.Profiles;
using Bots.DungeonBuddyDll.Attributes;
using Bots.DungeonBuddyDll.Helpers;
using AvoidanceManager = Bots.DungeonBuddyDll.Avoidance.AvoidanceManager;

namespace Bots.DungeonBuddyDll.Dungeon_Scripts.Mists_of_Pandaria
#else
    using Bots.DungeonBuddy.Profiles;
    using Bots.DungeonBuddy.Attributes;
    using Bots.DungeonBuddy.Helpers;
    using AvoidanceManager = Bots.DungeonBuddy.Avoidance.AvoidanceManager;
namespace Bots.DungeonBuddy.Dungeon_Scripts.Mists_of_Pandaria
#endif
{

    #region Normal Difficulty

    public class BattleOnTheHighSeasHorde : Dungeon
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 654; }
        }

        public override void RemoveTargetsFilter(List<WoWObject> units)
        {
            var myLoc = Me.Location;
            units.RemoveAll(
                ret =>
                    {
                        var unit = ret.ToUnit();
                        if (unit != null)
                        {
                            // try to elimination units that are on another boat.
                            if (unit.DistanceSqr > 50*50 || !Navigator.CanNavigateFully(myLoc, unit.Location))
                                return true;
                        }
                        return false;
                    });
        }

        public override void IncludeTargetsFilter(List<WoWObject> incomingunits, HashSet<WoWObject> outgoingunits)
        {
            foreach (var obj in incomingunits)
            {
                var unit = obj as WoWUnit;
                if (unit != null)
                {
                    if (unit.Combat && unit.IsHostile && unit.Distance < 40)
                        outgoingunits.Add(unit);
                }
            }
        }

        public override void WeighTargetsFilter(List<Targeting.TargetPriority> units)
        {
            foreach (var priority in units)
            {
                var unit = priority.Object as WoWUnit;
                if (unit != null) {}
            }
        }

        #endregion

        private const uint BarrelofOrangesId = 71054;
        private const uint BombsAwayTargetDummyId = 71024;
        private const uint TransportCannonId = 71331;
        private const uint TransportCannon2Id = 71167;
        private const uint RopeLadderId = 216077;

        #region Ship Navigation

        private const uint RopePile2Id = 219270;

        private const uint RopePileId = 216074;
        private readonly WaitTimer _interactTimer = new WaitTimer(TimeSpan.FromSeconds(4));


        private readonly List<List<WoWPoint>> _shipAreas = new List<List<WoWPoint>>
                                                           { // North horde ship
                                                               new List<WoWPoint>
                                                               {
                                                                   new WoWPoint(2452.808, -4049.638, 26.6615),
                                                                   new WoWPoint(2460.377, -4119.236, 21.47732),
                                                               },
                                                               // center horde ship
                                                               new List<WoWPoint>
                                                               {
                                                                   new WoWPoint(2313.285, -4193.405, 8.049974),
                                                                   new WoWPoint(2341.682, -4250.631, 23.9635),
                                                               },
                                                               // center alliance ship
                                                               new List<WoWPoint>
                                                               {
                                                                   new WoWPoint(2355.939, -4167.268, 15.23),
                                                                   new WoWPoint(2336.548, -4107.725, 20.35676),
                                                               },
                                                               // south alliance ship
                                                               new List<WoWPoint>
                                                               {
                                                                   new WoWPoint(2170.883, -4199.843, 21.14248),
                                                                   new WoWPoint(2164.876, -4261.232, 14.91238)
                                                               },
                                                           };

        private uint[] _netIds = new uint[] {216078, 216076};

        public override MoveResult MoveTo(WoWPoint location)
        {
            if (Me.MapId != LfgDungeon.MapId)
                return MoveResult.Failed;

            if (!_interactTimer.IsFinished)
                return MoveResult.Moved;

            if (Me.TransportGuid != 0 || Me.HasAura("Rope Cosmetic"))
            {
                Navigator.Clear();
                Navigator.NavigationProvider.StuckHandler.Reset();
                return MoveResult.Moved;
            }
            if (Me.IsSwimming)
            {
                var ropeLadder =
                    ObjectManager.GetObjectsOfTypeFast<WoWGameObject>()
                                 .Where(g => g.Entry == RopeLadderId || _netIds.Contains(g.Entry))
                                 .OrderBy(g => g.Distance2DSqr)
                                 .FirstOrDefault();
                if (ropeLadder != null)
                {
                    if (ropeLadder.WithinInteractRange)
                    {
                        if (Me.IsMoving)
                            WoWMovement.MoveStop();
                        ropeLadder.Interact();
                        _interactTimer.Reset();
                        Navigator.Clear();
                        return MoveResult.Moved;
                    }
                    return Navigator.MoveTo(ropeLadder.Location);
                }
            }


            var myLoc = Me.Location;
            var currentShip = GetBoatFromLocation(myLoc);
            var destinationShip = GetBoatFromLocation(location);
            if (currentShip != destinationShip)
            {
                // wait... 
                if ((int) currentShip > (int) destinationShip)
                {
                    return MoveResult.Moved;
                }
                var transportEntry = GetTransportIdForShip(currentShip);
                WoWObject transport = ObjectManager.ObjectList.FirstOrDefault(o => o.Entry == transportEntry && o.Distance <= 60);
                if (transport != null)
                {
                    if (transport.WithinInteractRange)
                    {
                        transport.Interact();
                        _interactTimer.Reset();
                        return MoveResult.Moved;
                    }
                    return Navigator.MoveTo(transport.Location);
                }
            }
            return base.MoveTo(location);
        }

        private uint GetTransportIdForShip(ShipIdentification ship)
        {
            switch (ship)
            {
                case ShipIdentification.NorthHordeShip:
                    return TransportCannonId;
                case ShipIdentification.CenterAllianceShip:
                    return RopePileId;
                case ShipIdentification.CenterHordeShip:
                    var stage = ScriptHelpers.CurrentScenarioInfo.CurrentStage;
                    return stage.StageNumber < 4 ? RopePile2Id : TransportCannon2Id;
            }
            return 0;
        }

        private ShipIdentification GetBoatFromLocation(WoWPoint myLoc)
        {
            for (int i = 0; i < _shipAreas.Count; i++)
            {
                var nearestPoint = myLoc.GetNearestPointOnLine(_shipAreas[i][0], _shipAreas[i][1]);
                if (myLoc.Distance2DSqr(nearestPoint) <= 20*20)
                {
                    return (ShipIdentification) i;
                }
            }
            return ShipIdentification.None;
        }


        private enum ShipIdentification
        {
            None = -1,
            NorthHordeShip,
            CenterHordeShip,
            CenterAllianceShip,
            SouthAllianceShip,
        }

        #endregion

        private readonly WoWPoint stage4Loc = new WoWPoint(2167.513, -4231.449, 14.31543);

        private static LocalPlayer Me
        {
            get { return StyxWoW.Me; }
        }

        [EncounterHandler(0)]
        public Composite RootEncounter()
        {
            AddAvoidObject(ctx => !Me.IsMoving, 4, BombsAwayTargetDummyId);
            return new PrioritySelector(
                // drink orange juice to heal.
                HealBehavior());
        }

        private Composite HealBehavior()
        {
            WoWUnit target;
            WoWObject healObject = null;

            return
                new Decorator(
                    ctx =>
                    Me.HealthPercent < 60 && !Targeting.Instance.TargetList.Any(t => t.IsWithinMeleeRange) &&
                    (healObject =
                     ObjectManager.ObjectList.Where(o => o.Entry == BarrelofOrangesId && o.Distance <= 40 && !AvoidanceManager.Avoids.Any(a => a.IsPointInAvoid(o.Location)))
                                  .OrderBy(o => o.DistanceSqr)
                                  .FirstOrDefault()) != null,
                    new PrioritySelector(
                        new Decorator(
                            ctx => Me.Location.Distance(healObject.Location) > 3,
                            new Action(ctx => Navigator.GetRunStatusFromMoveResult(Navigator.MoveTo(healObject.Location)))),
                        new Decorator(
                            ctx => Me.Location.Distance(healObject.Location) <= 3,
                            new PrioritySelector(
                                new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())),
                                new Sequence(new Action(ctx => healObject.Interact()), new WaitContinue(2, ctx => false, new ActionAlwaysSucceed()))))));
        }

        [ScenarioStage(1)]
        public Composite StageOneEncounter()
        {
            var stage1Loc = new WoWPoint(2461.149, -4088.688, 14.61192);
            return
                new PrioritySelector(
                    new Decorator(ctx => Targeting.Instance.IsEmpty() && Me.Location.DistanceSqr(stage1Loc) > 10*10, new Action(ctx => Navigator.MoveTo(stage1Loc))),
                    new Decorator(ctx => Targeting.Instance.IsEmpty() && Me.Location.DistanceSqr(stage1Loc) <= 10*10, new ActionAlwaysSucceed()));
        }

        [ScenarioStage(2)]
        public Composite StageTwoEncounter()
        {
            const uint explosiveBarrelId = 70755;
            const uint bombId = 67403;
            const int fuseVisualAuraId = 141068;
            const int fireAuraId = 143222;
            AddAvoidObject(ctx => true, 4, o => o.Entry == bombId && o.ToUnit().HasAura("Bomb!"));
            AddAvoidObject(ctx => true, 4, o => o.Entry == explosiveBarrelId && o.ToUnit().HasAura(fuseVisualAuraId));
            AddAvoidObject(ctx => true, 4, o => o is WoWAreaTrigger && ((WoWAreaTrigger) o).SpellId == fireAuraId);

            WoWUnit barrel = null;

            const uint barrelofExplosivesId = 71106;

            var stage2Loc = new WoWPoint(2337.642, -4127.524, 14.26143);
            return
                new PrioritySelector(
                    ctx =>
                    barrel =
                    ObjectManager.GetObjectsOfTypeFast<WoWUnit>().Where(u => u.Entry == barrelofExplosivesId && u.Distance <= 40).OrderBy(u => u.DistanceSqr).FirstOrDefault(),
                    new Decorator(ctx => barrel != null && !barrel.WithinInteractRange, new Action(ctx => Navigator.MoveTo(barrel.Location))),
                    new Decorator(
                        ctx => barrel != null && barrel.WithinInteractRange,
                        new Action(
                            ctx =>
                                {
                                    if (!Me.IsCasting)
                                    {
                                        if (Me.IsMoving)
                                            WoWMovement.MoveStop();
                                        barrel.Interact();
                                    }
                                })),
                    new Decorator(ctx => GetBoatFromLocation(Me.Location) == ShipIdentification.CenterAllianceShip, ScriptHelpers.CreateClearArea(() => stage2Loc, 40)),
                    new Decorator(
                        ctx =>
                        Targeting.Instance.IsEmpty() && GetBoatFromLocation(Me.Location) != ShipIdentification.CenterAllianceShip && Me.IsTank() && Me.HealthPercent > 80,
                        new Action(ctx => Navigator.MoveTo(stage2Loc))));
        }

        [EncounterHandler(71330, "Lieutenant Fizzboil")]
        public Composite LieutenantFizzelEncounter()
        {
            WoWUnit boss = null;
            return new PrioritySelector(
                ctx => boss = ctx as WoWUnit, ScriptHelpers.CreateDispellEnemy("Volatile Concoction", ScriptHelpers.EnemyDispellType.Magic, () => boss));
        }

        [ScenarioStage(3)]
        public Composite StageThreeEncounter()
        {
            ScenarioStage stage = null;
            WoWUnit barrel = null;
            const uint plantExplosivesId = 67394;
            var stage3Loc = new WoWPoint(2337.642, -4127.524, 14.26143);
            return new PrioritySelector(
                ctx =>
                    {
                        barrel =
                            ObjectManager.GetObjectsOfTypeFast<WoWUnit>()
                                         .Where(u => u.Entry == plantExplosivesId && u.HasAura("Empty"))
                                         .OrderBy(u => u.DistanceSqr)
                                         .FirstOrDefault();
                        return stage = ctx as ScenarioStage;
                    },
                // plant explosives..
                new Decorator(
                    ctx => !stage.GetStep(1).IsComplete,
                    new PrioritySelector(
                        new Decorator(ctx => barrel != null && !barrel.WithinInteractRange, new Action(ctx => Navigator.MoveTo(barrel.Location))),
                        new Decorator(
                            ctx => barrel != null && barrel.WithinInteractRange,
                            new Action(
                                ctx =>
                                    {
                                        if (!Me.IsCasting)
                                        {
                                            if (Me.IsMoving)
                                                WoWMovement.MoveStop();
                                            barrel.Interact();
                                        }
                                    })),
                        new Decorator(ctx => Me.Location.DistanceSqr(stage3Loc) > 30*30, new Action(ctx => Navigator.MoveTo(stage3Loc))))),
                new Decorator(ctx => stage.GetStep(1).IsComplete, new Action(ctx => Navigator.MoveTo(stage4Loc))));
        }

        [ScenarioStage(4)]
        public Composite StageFourEncounter()
        {
            return
                new PrioritySelector(
                    new Decorator(ctx => GetBoatFromLocation(Me.Location) == ShipIdentification.SouthAllianceShip, ScriptHelpers.CreateClearArea(() => stage4Loc, 60)),
                    new Decorator(
                        ctx => Targeting.Instance.IsEmpty() && GetBoatFromLocation(Me.Location) != ShipIdentification.SouthAllianceShip && Me.IsTank() && Me.HealthPercent > 80,
                        new Action(ctx => Navigator.MoveTo(stage4Loc))));
        }

        [EncounterHandler(71327, "Final Boss")]
        public Composite FinalBossEncounter()
        {
            WoWUnit boss = null;
            WoWGameObject rapier = null;
            const int duelistAuraId = 141153;
            const uint rapierId = 219304;
            const int verticalSlashSpellId = 141187;
            const int ripsoteId = 141154;
            const int rapierParryId = 141152;
            SpellActionButton extraActionButton = SpellActionButton.ExtraActionButton;

            return new PrioritySelector(
                ctx =>
                    {
                        if (Me.IsTank())
                            extraActionButton.Update();
                        return boss = ctx as WoWUnit;
                    },
                // Pickup rapier if tank.
                new Decorator(
                    ctx => Me.IsTank() && !Me.HasAura(duelistAuraId) && (rapier = ObjectManager.ObjectList.FirstOrDefault(o => o.Entry == rapierId) as WoWGameObject) != null,
                    new PrioritySelector(
                        new Decorator(
                            ctx => rapier.WithinInteractRange,
                            new PrioritySelector(new Decorator(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())), new Action(ctx => rapier.Interact()))),
                        new Decorator(ctx => !rapier.WithinInteractRange, new Action(ctx => Navigator.MoveTo(rapier.Location))))),
                // ripsote
                new Decorator(ctx => extraActionButton.SpellId == ripsoteId && boss.IsWithinMeleeRange, new Action(ctx => extraActionButton.Use())),
                // Rapier Usage Behavior
                new Decorator(
                    ctx => Me.HasAura(duelistAuraId),
                    new PrioritySelector(
                        new Decorator(
                            ctx => boss.CastingSpellId == verticalSlashSpellId && extraActionButton.SpellId == rapierParryId,
                            new Sequence(
                                new DecoratorContinue(ctx => Me.IsMoving, new Action(ctx => WoWMovement.MoveStop())),
                                new WaitContinue(1, ctx => !Me.IsMoving, new ActionAlwaysSucceed()),
                                new Action(ctx => boss.Face()),
                                new Action(ctx => extraActionButton.Use()),
                                new WaitContinue(2, ctx => boss.CastingSpellId != verticalSlashSpellId, new ActionAlwaysSucceed()))))));
        }

        #region Embedded type: SpellActionButton. Todo: Remove once this type is added to core.

        public class SpellActionButton
        {
            public int Slot { get; private set; }
            private int _cooldownDuration;
            private long _cooldownStart;

            public SpellActionButton(int slot)
            {
                Slot = slot;
                Update();
            }

            /// <summary>
            ///     return true if action can be used and is off cooldown
            /// </summary>
            public bool CanUse
            {
                get { return IsEnabled && !OnCooldown; }
            }

            public bool IsEnabled { get; private set; }
            public bool IsValid { get; private set; }

            public TimeSpan CoolDownLeft
            {
                get
                {
                    var elapsedMs = Environment.TickCount - _cooldownStart;
                    if (!IsValid || _cooldownStart == 0 || elapsedMs >= _cooldownDuration)
                        return TimeSpan.Zero;
                    return TimeSpan.FromMilliseconds(_cooldownDuration - elapsedMs);
                }
            }

            public bool OnCooldown
            {
                get { return CoolDownLeft != TimeSpan.Zero && Charges == 0; }
            }

            public WoWSpell Spell
            {
                get { return WoWSpell.FromId(SpellId); }
            }

            public int SpellId { get; private set; }
            public int Charges { get; private set; }
            public int MaxCharges { get; private set; }

            public void Update()
            {
                using (StyxWoW.Memory.AcquireFrame())
                {
                    IsValid = Lua.GetReturnVal<bool>(string.Format("return HasAction({0})", Slot), 0);
                    if (!IsValid)
                    {
                        IsEnabled = false;
                        _cooldownStart = _cooldownDuration = SpellId = 0;
                    }
                    else
                    {
                        var lua = string.Format("isUsable, notEnoughMana = IsUsableAction({0}) if isUsable and notEnoughMana == nil then return 1 end return nil", Slot);
                        IsEnabled = Lua.GetReturnVal<bool>(lua, 0);
                        var rawCooldownData = Lua.GetReturnValues(string.Format("return GetActionCooldown({0})", Slot));
                        SpellId = Lua.GetReturnVal<int>(string.Format("return GetActionInfo({0})", Slot), 1);
                        _cooldownStart = (long) (float.Parse(rawCooldownData[0])*1000);
                        _cooldownDuration = int.Parse(rawCooldownData[1])*1000;
                        Charges = int.Parse(rawCooldownData[3]);
                        MaxCharges = int.Parse(rawCooldownData[4]);
                    }
                }
            }

            public void Use()
            {
                Lua.DoString("UseAction({0})", Slot);
            }

            #region Static Members.

            /// <summary>Gets the extra action button.</summary>
            public static SpellActionButton ExtraActionButton
            {
                get { return new SpellActionButton(169); }
            }

            /// <summary>
            /// returns active pet's list of actions. Note: some pet actions such as 'Attack' have no spell Id
            /// </summary>
            public static List<SpellActionButton> PetActionButtons
            {
                get
                {
                    using (StyxWoW.Memory.AcquireFrame())
                    {
                        var actions = new List<SpellActionButton>(10);
                        const int startI = 182;
                        for (int i = 0; i < 12; i++)
                        {
                            var action = new SpellActionButton(i + startI);
                            if (action.IsValid)
                                actions.Add(action);
                        }
                        return actions;
                    }
                }
            }

            public static List<SpellActionButton> VehicleActionButtons
            {
                get
                {
                    using (StyxWoW.Memory.AcquireFrame())
                    {
                        var actions = new List<SpellActionButton>(10);
                        const int startI = 133;
                        for (int i = 0; i < 12; i++)
                        {
                            var action = new SpellActionButton(i + startI);
                            if (action.IsValid)
                                actions.Add(action);
                        }
                        return actions;
                    }
                }
            }

            #endregion

        }

        #endregion

    }

    #endregion


    #region Heroic Difficulty

    public class BattleOnTheHighSeasHeroicHorde : BattleOnTheHighSeasHorde
    {
        #region Overrides of Dungeon

        public override uint DungeonId
        {
            get { return 652; }
        }

        #endregion


    }

    #endregion

}